typedef void VOID;
typedef char CHAR;
typedef unsigned char BYTE;
typedef unsigned short WORD;

#ifdef __amd64__
typedef unsigned long long DWORD;
#else
typedef unsigned long DWORD;
#endif

typedef DWORD DWORD_PTR;

typedef VOID *LPVOID;
typedef BYTE *LPBYTE;
typedef WORD *LPWORD;
typedef DWORD *LPDWORD;

typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;

typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;

#define DESC_G_4K 0x00800000
#define DESC_D_32 0x00400000
#define DESC_L 0x00200000
#define DESC_AVL 0x00100000
#define DESC_P 0x00008000
#define DESC_DPL_0 0x00000000
#define DESC_DPL_1 0x00002000
#define DESC_DPL_2 0x00004000
#define DESC_DPL_3 0x00006000
#define DESC_TYPE_SYSTEM 0x00000000
#define DESC_TYPE_CODE 0x00001800
#define DESC_TYPE_DATA 0x00001000
#define DESC_CODE_CONFIRMING 0x00000400
#define DESC_CODE_READABLE 0x00000200
#define DESC_CODE_ACCESSED 0x00000100
#define DESC_DATA_EXTEND_DIR 0x00000400
#define DESC_DATA_WRITABLE 0x00000200
#define DESC_DATA_ACCESSED 0x00000100

#define INTR_TYPE_INT16 0x0600
#define INTR_TYPE_TRAP16 0x0700
#define INTR_TYPE_INT32 0x0E00
#define INTR_TYPE_TRAP32 0x0F00
#define INTR_TYPE_TASK32 0x0500

#define SEG_RPL0 0x0000
#define SEG_RPL1 0x0001
#define SEG_RPL2 0x0002
#define SEG_RPL3 0x0003

#define SEG_TI_GDT 0x0000
#define SEG_TI_LDT 0x0004

#define NULL (0)

#define FILE_ERROR_NOERR 0
#define FILE_ERROR_NOT_FOUND 1
#define FILE_ERROR_NULL_POINTER 2
#define FILE_ERROR_TOO_BIG 3
#define FILE_ERROR_VOLUME_NOT_ENOUGH 4
#define FILE_ERROR_ILLEGAL_ARGUMENT 5
#define FILE_ERROR_TYPE_ERROR 6

typedef BYTE MUTEX;

typedef char *va_list;
#define va_start(ap, v) (ap = (char *)&v)
#define va_end(ap) (ap = NULL)
#define va_arg(ap, t) *((t *)(ap += 4))

struct __table {
  WORD Limit;
  DWORD_PTR Base;
} __attribute__((packed)) _GDT, _IDT;

void putchar(char ch);
void lock(MUTEX *mutex);
void unlock(MUTEX *mutex);
void ReadDisk(DWORD LBAAddr, BYTE Number, void *lpBuf);
void WriteDisk(DWORD LBAAddr, BYTE Number, const void *lpBuf);
WORD GetCursorPos();
void SetCursorPos(WORD pos);
int strcmp(const char *str1, const char *str2);
int strncmp(const char *str1, const char *str2, int len);
void rollScreen();
void changeLine();
void puts(const char *str);
void tickProc();
void strcpy(char *dest, const char *src);
void memcpy(void *dest, const void *src, int len);
void memset(void *dest, DWORD val, DWORD size);
BYTE inb(WORD port);
WORD inw(WORD port);
DWORD indw(WORD port);
void outb(WORD port, BYTE data);
void outw(WORD port, WORD data);
void outdw(WORD port, DWORD data);
int sprintf(char *buf, const char *format, ...);
int sscanf(char *buf, const char *format, ...);
char getch();
void halt();
DWORD AllocSector();
void FreeSector(DWORD addr);
void replace(char *str, char src, char dest);
char **split(const char *str, char ch);
DWORD findpath(DWORD base, const char *path);
int strlen(const char *str);
void panic(const char *str);
void drawPixel(WORD x, WORD y, BYTE pixel);

#define RtlZeroMemory(d, s) memset(d, 0, s)

const char KeyTable[0x100] = {
    0,    0,    '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-',  '=',
    '\b', 0,    'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[',  ']',
    '\n', 0,    'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', 0,
    0,    '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0,    '*',
    0,    ' ',  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,    0,
    0,    '7',  '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0',  '.',
    0,    0,    0,   0,   0,   0,   0,   0,   0,   0,   0,   0};
const char HexTable[0x10] = {'0', '1', '2', '3', '4', '5', '6', '7',
                             '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

#define FILE_TYPE_UNKNOWN 0x00000000
#define FILE_TYPE_DEFAULT 0x00000001
#define FILE_TYPE_FOLDER 0x00000002

#define FILE_INODE_MAGIC 0x454C4946

typedef struct {
  CHAR FileName[0x80]; //文件名长度 0x80 => 128
  DWORD Addr[0x5C]; //文件长度 0x5C * 0x200 => 0xB800 => 47104 文件夹文件数 0x5C
                    //=> 92
  DWORD Super;
  DWORD Length;
  DWORD Type;
  DWORD Magic;
} inode;

#define BITMAP_SECTOR_COUNT 0x10000

BYTE $Bitmap[BITMAP_SECTOR_COUNT];
inode $Root;
static inode tempNode, tempNode2;
static DWORD curBase = 0x180;

//#define DEBUG

//注:这里代码是保护模式就开始运行的
//如果你想更好地管理内存,比如分页什么的
//代码自己写,详见Intel开发手册
int __main() {
  SetCursorPos(0);
  for (DWORD_PTR i = 0xB8000; i < 0xB9040; i += 4)
    *(DWORD *)i = 0x17001700;
  puts("Hello World!\n");
  outb(0x43, 0x36);  // 00(counter 0 port 0x40)11(8l8h)x11(square wave)0(bin)
  outw(0x40, 11932); // 1.1932MHz
  *(DWORD_PTR *)0x50C = (DWORD_PTR)tickProc;
  //while(inb(0x64)&0x02);
  //outb(0x64, 0x60);
  //while(inb(0x64)&0x02);
  //outb(0x60, 0x47);
  //while(inb(0x64)&0x02);
  //outb(0x64, 0xD4);
  //while(inb(0x64)&0x02);
  //outb(0x60, 0xF4);
  asm("sgdt (%%eax)" ::"a"(&_GDT));
  asm("sidt (%%eax)" ::"a"(&_IDT));
  static char buf[0x1000];
  static char tempBuf[0x200];
  static char curPath[0x1000];
  *curPath = '\000';
#ifdef DEBUG
  sprintf(buf, "%x\n", (DWORD_PTR)__main);
  puts(buf);
#endif
  ReadDisk(0x180, 1, &$Root);
  if ($Root.Magic != FILE_INODE_MAGIC) {
    puts("Initializing root node...\n");
    RtlZeroMemory(&$Root, sizeof(inode));
    $Root.Type = FILE_TYPE_FOLDER;
    $Root.Magic = FILE_INODE_MAGIC;
    memcpy($Root.FileName, "DRIVE", 6);
    WriteDisk(0x180, 1, &$Root);
    RtlZeroMemory($Bitmap, 0x10000);
    $Bitmap[0] |= 1;
    WriteDisk(0x100, 0x80, $Bitmap);
  } else
    ReadDisk(0x100, 0x80, $Bitmap);
  //sprintf(buf, "%x:%x\n", *(WORD *)(DWORD_PTR)0x42, *(WORD *)(DWORD_PTR)0x40);
  //puts(buf);
#ifdef GRAPHIC
  for (WORD i = 50; i < 150; i++) {
    drawPixel(128, i, 0x01);
    drawPixel(192, i, 0x01);
  }
  for (WORD i = 128; i < 192; i++) {
    drawPixel(i, 50, 0x01);
    drawPixel(i, 150, 0x01);
  }
#endif
  while (1) {
    DWORD index = 0;
    static char *argv[0x100];
    puts(curPath);
    puts(">>>");
    while (1) {
      char &ch = buf[index];
      ch = getch();
      if (ch == '\b') {
        if (index <= 0)
          continue;
        WORD pos = GetCursorPos() - 1;
        *(BYTE *)(0xB8000 + 2 * (DWORD)pos) = '\000';
        SetCursorPos(pos);
        index--;
        continue;
      }
      putchar(ch);
      if (ch == '\n') {
        ch = '\000';
        break;
      }
      if (index < 0x100)
        index++;
    }
    index = 0;
    if (*buf != ' ')
      argv[index++] = buf;
    else
      *buf = '\000';
    for (DWORD i = 1; buf[i]; i++)
      if (buf[i] == ' ')
        buf[i] = '\000';
      else if (buf[i - 1] == '\000')
        argv[index++] = buf + i;
    argv[index] = NULL;
    if (!strcmp(*argv, "mbr")) {
      ReadDisk(0, 1, buf);
      for (DWORD i = 0; i < 0x100; i++)
        putchar(buf[i]);
      changeLine();
    } else if (!strcmp(*argv, "read")) {
      DWORD addr;
      if (!argv[1])
        continue;
      sscanf(argv[1], "%d", &addr);
      ReadDisk(addr, 1, buf);
      for (DWORD i = 0; i < 0x100; i++)
        putchar(buf[i]);
      changeLine();
    } else if (!strcmp(*argv, "in")) {
      WORD addr;
      if (!argv[1])
        continue;
      sscanf(argv[1], "%d", &addr);
      sprintf(buf, "%x\n", inb(addr));
      puts(buf);
    } else if (!strcmp(*argv, "out")) {
      WORD addr, val;
      if (!argv[1] || !argv[2])
        continue;
      sscanf(argv[1], "%d", &addr);
      sscanf(argv[2], "%d", &val);
      outb(addr, val);
    } else if (!strcmp(*argv, "msg")) {
      puts("Hello World!\n");
    } else if (!strcmp(*argv, "graphic")) {
      outb(0x3D8, inb(0x3D8) | 0x2);
      for (DWORD_PTR i = 0xA0000; i < 0xA0140; i++)
        *(BYTE *)i = 0x10;
      for (DWORD i = 0; i < 100; i++)
        halt();
      outb(0x3D8, inb(0x3D8) & 0xFD);
    } else if (!strcmp(*argv, "create")) {
      if (!argv[1] || $Root.Length > 0x34)
        continue;
      bool dir = false;
      if (argv[2] && !strcmp(argv[1], "dir")) {
        argv[1] = argv[2];
        dir = true;
      }
      replace(argv[1], '/', '\\');
      char **path = split(argv[1], '\\');
      DWORD cur = curBase;
      DWORD index;
      for (index = 0; path[index + 1]; index++)
        ;
      RtlZeroMemory(&tempNode, sizeof(inode));
      strcpy(tempNode.FileName, path[index]);
      for (DWORD i = 0; i < index; i++) {
        cur = findpath(cur, path[i]);
        if (!cur)
          break;
      }
      if (!cur) {
        puts("path not found.\n");
        continue;
      }
      ReadDisk(cur, 1, &tempNode2);
      tempNode2.Addr[tempNode2.Length] = AllocSector();
      tempNode.Type = (dir ? FILE_TYPE_FOLDER : FILE_TYPE_DEFAULT);
      tempNode.Super = cur;
      tempNode.Magic = FILE_INODE_MAGIC;
      WriteDisk(tempNode2.Addr[tempNode2.Length], 1, &tempNode);
#ifdef DEBUG
      sprintf(tempBuf, "name: %s\nalloc sector: %x\n", path[index],
              tempNode2.Addr[tempNode2.Length]);
      puts(tempBuf);
#endif
      ++tempNode2.Length;
      WriteDisk(cur, 1, &tempNode2);
    } else if (!strcmp(*argv, "delete")) {
      if (!argv[1]) {
        puts("argument not enough.\n");
        continue;
      }
      bool dir = false;
      if (argv[2] && !strcmp(argv[1], "dir")) {
        argv[1] = argv[2];
        dir = true;
      }
      DWORD addr = findpath(curBase, argv[1]);
      DWORD cur;
      if (!addr) {
        puts("File not found.\n");
        continue;
      }
#ifdef DEBUG
      sprintf(tempBuf, "address: %x\n", addr);
      puts(tempBuf);
#endif
      ReadDisk(addr, 1, &tempNode);
      if (dir) {
        if (tempNode.Type != FILE_TYPE_FOLDER) {
          puts("File type error.\n");
          continue;
        }
        if (tempNode.Length > 0) {
          puts("Directory is not empty.\n");
          continue;
        }
        if (tempNode.Super) {
          ReadDisk(tempNode.Super, 1, &tempNode2);
          for (DWORD i = 0; i < tempNode2.Length; i++)
            if (tempNode2.Addr[i] == addr) {
              tempNode2.Addr[i] = tempNode2.Addr[--tempNode2.Length];
              break;
            }
        }
        FreeSector(addr);
        continue;
      }
      if (tempNode.Type != FILE_TYPE_DEFAULT) {
        puts("File type error.\n");
        continue;
      }
      for (DWORD i = 0; i < ((tempNode.Length + 0x1FF) / 0x200); i++)
        FreeSector(tempNode.Addr[i]);
      FreeSector(addr);
      cur = tempNode.Super;
      ReadDisk(cur, 1, &tempNode);
      for (DWORD i = 0; i < tempNode.Length; i++)
        if (tempNode.Addr[i] == addr) {
          tempNode.Addr[i] = tempNode.Addr[--tempNode.Length];
          break;
        }
      WriteDisk(cur, 1, &tempNode);
    } else if (!strcmp(*argv, "append")) {
      if (!argv[1] || !argv[2]) {
        puts("argument not enough.\n");
        continue;
      }
      DWORD addr = findpath(curBase, argv[1]);
      if (!addr) {
        puts("File not found.\n");
        continue;
      }
      ReadDisk(addr, 1, &tempNode);
      if (tempNode.Type != FILE_TYPE_DEFAULT) {
        puts("File type error.\n");
        continue;
      }
      DWORD pos = tempNode.Length;
      int len = strlen(argv[2]);
      DWORD num1 = (tempNode.Length + 0x1FF) / 0x200;
      tempNode.Length += len;
      for (DWORD i = num1; i < (tempNode.Length + 0x1FF) / 0x200; i++)
        tempNode.Addr[i] = AllocSector();
      if (pos & 0x1FF) {
        ReadDisk(tempNode.Addr[num1 - 1], 1, tempBuf);
        memcpy(tempBuf + (pos & 0x1FF), argv[2], 0x200 - (pos & 0x1FF));
        WriteDisk(tempNode.Addr[num1 - 1], 1, tempBuf);
        DWORD temp = (0x200 - (pos & 0x1FF));
        len -= temp;
        argv[2] += temp;
      }
      for (DWORD i = num1; len > 0; i++) {
        WriteDisk(tempNode.Addr[i], 1, argv[2]);
        argv[2] += 0x200;
        len -= 0x200;
      }
      WriteDisk(addr, 1, &tempNode);
    } else if (!strcmp(*argv, "print")) {
      if (!argv[1]) {
        puts("argument not enough.\n");
        continue;
      }
      DWORD addr = findpath(curBase, argv[1]);
      if (!addr) {
        puts("File not found.\n");
        continue;
      }
#ifdef DEBUG
      sprintf(tempBuf, "address: %x\n", addr);
      puts(tempBuf);
#endif
      ReadDisk(addr, 1, &tempNode);
      if (tempNode.Type != FILE_TYPE_DEFAULT) {
        puts("File type error.\n");
        continue;
      }
      for (DWORD i = 0; i < ((tempNode.Length + 0x1FF) / 0x200); i++) {
        ReadDisk(tempNode.Addr[i], 1, tempBuf);
        for (DWORD j = 0; j < 0x200 && j < tempNode.Length - i * 0x200; j++)
          putchar(tempBuf[j]);
      }
      putchar('\n');
    } else if (!strcmp(*argv, "dir")) {
      if (argv[1]) {
        DWORD addr = findpath(curBase, argv[1]);
        if (!addr) {
          puts("directory not found.\n");
          continue;
        }
        ReadDisk(addr, 1, &tempNode);
      } else
        ReadDisk(curBase, 1, &tempNode);
      static char item[0x100];
      puts("name                            type            size\n");
      for (DWORD i = 0; i < tempNode.Length; i++) {
        memset(item, ' ', 0x40);
        ReadDisk(tempNode.Addr[i], 1, &tempNode2);
        if (tempNode2.Magic != FILE_INODE_MAGIC) {
          puts("File magic error.\n");
          continue;
        }
        sprintf(item, "%s", tempNode2.FileName);
        item[strlen(tempNode2.FileName)] = ' ';
        switch (tempNode2.Type) {
        case FILE_TYPE_DEFAULT:
          sprintf(item + 0x20, "default");
          item[0x27] = ' ';
          break;
        case FILE_TYPE_FOLDER:
          sprintf(item + 0x20, "folder");
          item[0x26] = ' ';
          break;
        default:
          sprintf(item + 0x20, "unknown");
          item[0x27] = ' ';
          break;
        }
        sprintf(item + 0x30, "%d\n", tempNode2.Length);
        puts(item);
      }
    } else if (!strcmp(*argv, "cd")) {
      if (!argv[1])
        continue;
      DWORD pos = findpath(0x180, argv[1]);
      ReadDisk(pos, 1, &tempNode);
      if (!pos || tempNode.Type != FILE_TYPE_FOLDER) {
        puts("Directory not found.\n");
        continue;
      }
      curBase = pos;
      strcpy(curPath, argv[1]);
    } else if (!strcmp(*argv, "panic")) {
      if (!argv[1])
        continue;
      panic(argv[1]);
    } else {
      puts("unknown command:");
      puts(*argv);
      changeLine();
    }
  }
}

char getch() {
  static char (*getchar)(), ch;
  getchar = ((char (*)())(*(DWORD_PTR *)0x500));
  while (!(ch = KeyTable[(BYTE)getchar()]))
    ;
  //*(BYTE *)(DWORD_PTR)0xB8001 = ~*(BYTE *)(DWORD_PTR)0xB8001;
  return ch;
}

void tickProc() {
  static BYTE num;
  static WORD pos;
  pos = GetCursorPos();
  SetCursorPos(60);
  for (DWORD i = 0; i < 3; i++) {
    outb(0x70, 4 - i * 2);
    num = inb(0x71);
    putchar(HexTable[num >> 4]);
    putchar(HexTable[num & 0xf]);
    if (i < 2)
      putchar(':');
  }
  SetCursorPos(pos);
}

void changeLine() {
  WORD pos = (DWORD)GetCursorPos() / 80 * 80 + 80;
  if (pos >= 2000) {
    rollScreen();
    pos -= 80;
  }
  SetCursorPos(pos);
}

void rollScreen() {
  asm("mov $0,%esi\n"
      "mov $1000,%ecx\n"
      "1:"
      "gs mov 160(%esi),%eax\n"
      "gs mov %eax,(%esi)\n"
      "add $4,%esi\n"
      "loop 1b\n");
}

void putchar(char ch) {
  if (ch == '\000')
    return;
  if (ch == '\n') {
    changeLine();
    return;
  }
  DWORD index = GetCursorPos();
  asm("gs movb %%al,(,%%ebx,2)" ::"a"(ch), "b"(index));
  index++;
  SetCursorPos((WORD)index);
}

void puts(const char *str) {
  uint32_t i;
  uint16_t pos = GetCursorPos();
  for (i = 0; str[i]; i++) {
    if (str[i] == '\n') {
      changeLine();
      pos = GetCursorPos();
      continue;
    }
    asm("gs movb %%al,(,%%ebx,2)" ::"a"(str[i]), "b"((uint32_t)pos));
    pos++;
  }
  SetCursorPos(pos);
}

void lock(MUTEX *mutex) {
  asm("mov $1,%%al\n"
      "1:"
      "lock xchg (%%ebx),%%al\n"
      "test %%al,%%al\n"
      "jnz 1b\n" ::"b"(mutex));
}

void unlock(MUTEX *mutex) { *mutex = 0; }

void ReadDisk(DWORD LBAAddr, BYTE Number, void *lpBuf) {
  outb(0x1f2, Number);
  outb(0x1f3, LBAAddr & 0xff);
  outb(0x1f4, (LBAAddr >> 8) & 0xff);
  outb(0x1f5, (LBAAddr >> 16) & 0xff);
  outb(0x1f6, ((LBAAddr >> 24) & 0xf) | 0xe0); // 0xe0:111 0 0000 LBA Drive0
  outb(0x1f7, 0x20);
  while ((inb(0x1f7) & 0x88) != 0x8)
    ;
  for (DWORD i = 0; i < 0x80 * (DWORD)Number; i++)
    ((DWORD *)lpBuf)[i] = indw(0x1f0);
}

void WriteDisk(DWORD LBAAddr, BYTE Number, const void *lpBuf) {
  outb(0x1f2, Number);
  outb(0x1f3, LBAAddr & 0xff);
  outb(0x1f4, (LBAAddr >> 8) & 0xff);
  outb(0x1f5, (LBAAddr >> 16) & 0xff);
  outb(0x1f6, ((LBAAddr >> 24) & 0xf) | 0xe0); // 0xe0:111 0 0000 LBA Drive0
  outb(0x1f7, 0x30);
  while ((inb(0x1f7) & 0x88) != 0x8)
    ;
  for (DWORD i = 0; i < 0x80 * (DWORD)Number; i++)
    outdw(0x1f0, ((DWORD *)lpBuf)[i]);
}

WORD GetCursorPos() {
  WORD ret = 0;
  outb(0x3d4, 0xf);
  ret |= inb(0x3d5);
  outb(0x3d4, 0xe);
  ret |= ((WORD)inb(0x3d5) << 8);
  return ret;
}

void SetCursorPos(WORD pos) {
  outb(0x3d4, 0xf);
  outb(0x3d5, (BYTE)pos);
  outb(0x3d4, 0xe);
  outb(0x3d5, (BYTE)(pos >> 8));
}

int strcmp(const char *str1, const char *str2) {
  int i;
  for (i = 0; str1[i]; i++)
    if (str1[i] == str2[i])
      continue;
    else
      return str1[i] > str2[i] ? 1 : -1;
  return str2[i] == 0 ? 0 : (str1[i] > str2[i] ? 1 : -1);
}

int strncmp(const char *str1, const char *str2, int len) {
  int i;
  for (i = 0; str1[i] && i < len; i++)
    if (str1[i] == str2[i])
      continue;
    else
      return str1[i] > str2[i] ? 1 : -1;
  if (i >= len)
    return 0;
  return str2[i] == 0 ? 0 : (str1[i] > str2[i] ? 1 : -1);
}

void strcpy(char *dest, const char *src) {
  int i;
  for (i = 0; src[i]; i++)
    dest[i] = src[i];
  dest[i] = '\000';
}

void memcpy(void *dest, const void *src, int len) {
  int i;
  for (i = 0; i < len; i++)
    ((char *)dest)[i] = ((const char *)src)[i];
}

BYTE inb(WORD port) {
  BYTE ret;
  asm("in %%dx,%%al" : "=a"(ret) : "d"(port));
  return ret;
}

WORD inw(WORD port) {
  WORD ret;
  asm("in %%dx,%%ax" : "=a"(ret) : "d"(port));
  return ret;
}

DWORD indw(WORD port) {
  DWORD ret;
  asm("in %%dx,%%eax" : "=a"(ret) : "d"(port));
  return ret;
}

void outb(WORD port, BYTE data) { asm("out %%al,%%dx" ::"a"(data), "d"(port)); }

void outw(WORD port, WORD data) { asm("out %%ax,%%dx" ::"a"(data), "d"(port)); }

void outdw(WORD port, DWORD data) {
  asm("out %%eax,%%dx" ::"a"(data), "d"(port));
}

void halt() { asm("hlt"); }

int strlen(const char *str) {
  int i;
  for (i = 0; str[i]; i++)
    ;
  return i;
}

static void itoa(uint32_t value, char **buf_ptr_addr, uint8_t base) {
  uint32_t m = value % base;
  uint32_t i = value / base;
  if (i) { // recursion
    itoa(i, buf_ptr_addr, base);
  }
  if (m < 10) {
    *((*buf_ptr_addr)++) = m + '0'; //'0'~'9'
  } else {
    *((*buf_ptr_addr)++) = m - 10 + 'A'; //'A'~'F'
  }
}

static uint32_t atoi(char **buf_ptr_addr) {
  uint8_t base = 10;
  while (**buf_ptr_addr == ' ')
    ++*buf_ptr_addr;
  if (**buf_ptr_addr == '0') {
    if (*(++(*buf_ptr_addr)) == 'x') {
      base = 16;
      ++*buf_ptr_addr;
    } else
      base = 8;
  }
  uint32_t val = 0;
  while (**buf_ptr_addr != ' ') {
    char ch = **buf_ptr_addr;
    if (!ch)
      break;
    val *= (uint32_t)base;
    if (ch >= '0' && ch <= '9')
      val += ch - '0';
    else if (ch >= 'a' && ch <= 'f')
      val += ch - 'a' + 10;
    else if (ch >= 'A' && ch <= 'F')
      val += ch - 'A' + 10;
    ++*buf_ptr_addr;
  }
  return val;
}

uint32_t vsprintf(char *str, const char *format, va_list ap) {
  char *buf_ptr = str;
  const char *index_ptr = format;
  char index_char = *index_ptr;
  int32_t arg_int;
  char *arg_str;
  while (index_char) {
    if (index_char != '%') {
      *(buf_ptr++) = index_char;
      index_char = *(++index_ptr);
      continue;
    }
    index_char = *(++index_ptr); // get symbols of %
    switch (index_char) {
    case 's':
      arg_str = va_arg(ap, char *);
      strcpy(buf_ptr, arg_str);
      buf_ptr += strlen(arg_str);
      index_char = *(++index_ptr);
      break;

    case 'c':
      *(buf_ptr++) = va_arg(ap, char);
      index_char = *(++index_ptr);
      break;

    case 'd':
      arg_int = va_arg(ap, int);
      // negative
      if (arg_int < 0) {
        arg_int = 0 - arg_int;
        *buf_ptr++ = '-';
      }
      itoa(arg_int, &buf_ptr, 10);
      index_char = *(++index_ptr);
      break;
    case 'x':
      arg_int = va_arg(ap, int);
      itoa(arg_int, &buf_ptr, 16);
      index_char = *(++index_ptr); // skip %x
      break;
    default:
      break;
    }
  }
  *buf_ptr = '\000';
  return strlen(str);
}

int sprintf(char *buf, const char *format, ...) {
  va_list args;
  uint32_t retval;
  va_start(args, format);
  retval = vsprintf(buf, format, args);
  va_end(args);
  return retval;
}

uint32_t vsscanf(char *str, const char *format, va_list ap) {
  char *buf_ptr = str;
  const char *index_ptr = format;
  char index_char = *index_ptr;
  char *arg_str;
  while (index_char) {
    if (index_char != '%') {
      index_char = *(++index_ptr);
      continue;
    }
    index_char = *(++index_ptr); // get symbols of %
    switch (index_char) {
    case 's':
      arg_str = va_arg(ap, char *);
      while (*buf_ptr == ' ')
        buf_ptr++;
      while (*buf_ptr != ' ' && *buf_ptr)
        *(arg_str++) = *(buf_ptr++);
      *arg_str = '\000';
      break;

    case 'c':
      *va_arg(ap, char *) = *(buf_ptr++);
      break;

    case 'd':
      *va_arg(ap, int *) = atoi(&buf_ptr);
      break;

    default:
      break;
    }
  }
  return buf_ptr - str;
}

int sscanf(char *buf, const char *format, ...) {
  va_list args;
  uint32_t retval;
  va_start(args, format);
  retval = vsscanf(buf, format, args);
  va_end(args);
  return retval;
}

void memset(void *dest, DWORD val, DWORD size) {
  if (val & 0xFFFF0000) {
    for (DWORD_PTR i = 0; i < size; i += 4)
      *(DWORD *)((DWORD_PTR)dest + i) = val;
    return;
  }
  if (val & 0xFF00) {
    for (DWORD_PTR i = 0; i < size; i += 2)
      *(WORD *)((DWORD_PTR)dest + i) = (WORD)val;
    return;
  }
  for (DWORD_PTR i = 0; i < size; i++)
    *(BYTE *)((DWORD_PTR)dest + i) = (BYTE)val;
}

DWORD AllocSector() {
  for (DWORD i = 0; i < BITMAP_SECTOR_COUNT; i++) {
    if ($Bitmap[i] ^ 0xFF) {
      for (DWORD j = 0; j < 8; j++)
        if (!(($Bitmap[i] >> j) & 1)) {
          $Bitmap[i] |= (1 << j);
          WriteDisk(0x100 + i / 0x200, 1, $Bitmap + (i & 0xFFFFFE00));
          return 0x180 + 8 * i + j;
        }
    }
  }
  return -1;
}

void FreeSector(DWORD addr) {
  addr -= 0x180;
  $Bitmap[addr / 8] &= (0xFF ^ (1 << (addr & 7)));
  WriteDisk(0x100 + addr / 0x1000, 1, $Bitmap + (addr & 0xFFFF0000) / 8);
}

char **split(const char *str, char ch) {
  static char *argv[0x100];
  static char buf[0x1000];
  DWORD index = 0;
  strcpy(buf, str);
  if (*buf != ch)
    argv[index++] = buf;
  else
    *buf = '\000';
  for (DWORD i = 1; buf[i]; i++)
    if (buf[i] == ch)
      buf[i] = '\000';
    else if (buf[i - 1] == '\000')
      argv[index++] = buf + i;
  argv[index] = NULL;
#ifdef DEBUG
  static char temp[0x100];
  sprintf(temp, "split: arg count %d\n", index);
  puts(temp);
#endif
  return argv;
}

void replace(char *str, char src, char dest) {
  for (DWORD i = 0; str[i]; i++)
    if (str[i] == src)
      str[i] = dest;
}

DWORD findpath(DWORD base, const char *path) {
  static char buf[0x1000];
  strcpy(buf, path);
  replace(buf, '/', '\\');
  static char *argv[0x100];
  DWORD index = 0;
  if (*buf != '\\')
    argv[index++] = buf;
  else
    *buf = '\000';
  for (DWORD i = 1; buf[i]; i++)
    if (buf[i] == '\\')
      buf[i] = '\000';
    else if (buf[i - 1] == '\000')
      argv[index++] = buf + i;
  argv[index] = NULL;
  static inode node, next;
  bool found;
#ifdef DEBUG
  static char temp[0x100];
  sprintf(temp, "base: %x\n", base);
  puts(temp);
#endif
  for (index = 0; argv[index]; index++) {
    ReadDisk(base, 1, &node);
    if (!strcmp(argv[index], "..")) {
      base = node.Super;
      continue;
    }
    if (node.Type != FILE_TYPE_FOLDER) {
      puts("findpath: type error.\n");
      return NULL;
    }
    found = false;
    for (DWORD i = 0; i < node.Length; i++) {
      ReadDisk(node.Addr[i], 1, &next);
      if (next.Magic != FILE_INODE_MAGIC) {
        puts("findpath: magic error.\n");
        continue;
      }
      if (!strncmp(next.FileName, argv[index], 0x80)) {
        base = node.Addr[i];
#ifdef DEBUG
        sprintf(temp, "next: %x\n", base);
        puts(temp);
#endif
        found = true;
        break;
      }
    }
    if (!found) {
      puts("findpath: not found.\n");
      return NULL;
    }
  }
  return base;
}

void panic(const char *str) {
  SetCursorPos(0);
  for (DWORD_PTR i = 0xB8000; i < 0xB9040; i += 4)
    *(DWORD *)i = 0x4F004F00;
  puts(str);
  asm("cli\n"
      "hlt");
}

DWORD CreateFile(char *path) {
  if (!path)
    return FILE_ERROR_NULL_POINTER;
  replace(path, '/', '\\');
  char **dir = split(path, '\\');
  DWORD cur = curBase;
  DWORD index;
  for (index = 0; dir[index + 1]; index++)
    ;
  RtlZeroMemory(&tempNode, sizeof(inode));
  strcpy(tempNode.FileName, dir[index]);
  for (DWORD i = 0; i < index; i++) {
    cur = findpath(cur, dir[i]);
    if (!cur)
      return FILE_ERROR_NOT_FOUND;
  }
  ReadDisk(cur, 1, &tempNode2);
  if (tempNode2.Type != FILE_TYPE_FOLDER)
    return FILE_ERROR_TYPE_ERROR;
  if (tempNode2.Length >= 0x5C)
    return FILE_ERROR_TOO_BIG;
  tempNode2.Addr[tempNode2.Length] = AllocSector();
  if (tempNode2.Addr[tempNode2.Length] == (DWORD)(-1))
    return FILE_ERROR_VOLUME_NOT_ENOUGH;
  tempNode.Type = FILE_TYPE_DEFAULT;
  tempNode.Super = cur;
  tempNode.Magic = FILE_INODE_MAGIC;
  WriteDisk(tempNode2.Addr[tempNode2.Length], 1, &tempNode);
  ++tempNode2.Length;
  WriteDisk(cur, 1, &tempNode2);
  return FILE_ERROR_NOERR;
}

void drawPixel(WORD x, WORD y, BYTE pixel) {
  *(BYTE *)(DWORD_PTR)(0xA0000 + x + y * 0x140) = pixel;
}

// DWORD DeleteFile(char *path) {}

// DWORD AppendFile(char *path, const char *lpBuf, DWORD dwLength) {}

// DWORD CreateDirectory(char *path) {}

// DWORD DeleteDirectory(char *path) {}
